/*********************************************************************
 * Copyright (C) 2002 Andrew Khan
 * <p>
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * <p>
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * <p>
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ***************************************************************************/

package jxl.read.biff;

import jxl.WorkbookSettings;
import jxl.biff.BuiltInName;
import jxl.biff.IntegerHelper;
import jxl.biff.RecordData;
import jxl.biff.StringHelper;
import jxl.common.Assert;
import jxl.common.Logger;

import java.util.ArrayList;

/**
 * Holds an excel name record, and the details of the cells/ranges it refers
 * to
 */
public class NameRecord extends RecordData {
    /**
     * The logger
     */
    private static Logger logger = Logger.getLogger(NameRecord.class);

    /**
     * The name
     */
    private String name;

    /**
     * The built in name
     */
    private BuiltInName builtInName;

    /**
     * The 0-based index in the name table
     */
    private int index;

    /**
     * The 0-based index sheet reference for a record name
     *     0 is for a global reference
     */
    private int sheetRef = 0;

    /**
     * Indicates whether this is a biff8 name record.  Used during copying
     */
    private boolean isbiff8;

    /**
     * Dummy indicators for overloading the constructor
     */
    private static class Biff7 {
    }

    ;
    public static Biff7 biff7 = new Biff7();

    // Constants which refer to the name type
    private static final int commandMacro = 0xc;
    private static final int builtIn = 0x20;

    // Constants which refer to the parse tokens after the string
    private static final int cellReference = 0x3a;
    private static final int areaReference = 0x3b;
    private static final int subExpression = 0x29;
    private static final int union = 0x10;

    /**
     * A nested class to hold range information
     */
    public class NameRange {
        /**
         * The first column
         */
        private int columnFirst;

        /**
         * The first row
         */
        private int rowFirst;

        /**
         * The last column
         */
        private int columnLast;

        /**
         * The last row
         */
        private int rowLast;

        /**
         * The first sheet
         */
        private int externalSheet;

        /**
         * Constructor
         *
         * @param s1 the sheet
         * @param c1 the first column
         * @param r1 the first row
         * @param c2 the last column
         * @param r2 the last row
         */
        NameRange(int s1, int c1, int r1, int c2, int r2) {
            columnFirst = c1;
            rowFirst = r1;
            columnLast = c2;
            rowLast = r2;
            externalSheet = s1;
        }

        /**
         * Accessor for the first column
         *
         * @return the index of the first column
         */
        public int getFirstColumn() {
            return columnFirst;
        }

        /**
         * Accessor for the first row
         *
         * @return the index of the first row
         */
        public int getFirstRow() {
            return rowFirst;
        }

        /**
         * Accessor for the last column
         *
         * @return the index of the last column
         */
        public int getLastColumn() {
            return columnLast;
        }

        /**
         * Accessor for the last row
         *
         * @return the index of the last row
         */
        public int getLastRow() {
            return rowLast;
        }

        /**
         * Accessor for the first sheet
         *
         * @return the index of the external  sheet
         */
        public int getExternalSheet() {
            return externalSheet;
        }
    }

    /**
     * The ranges referenced by this name
     */
    private ArrayList ranges;

    /**
     * Constructs this object from the raw data
     *
     * @param t the raw data
     * @param ws the workbook settings
     * @param ind the index in the name table
     */
    NameRecord(Record t, WorkbookSettings ws, int ind) {
        super(t);
        index = ind;
        isbiff8 = true;

        try {
            ranges = new ArrayList();

            byte[] data = getRecord().getData();
            int option = IntegerHelper.getInt(data[0], data[1]);
            int length = data[3];
            sheetRef = IntegerHelper.getInt(data[8], data[9]);

            if ((option & builtIn) != 0) {
                builtInName = BuiltInName.getBuiltInName(data[15]);
            } else {
                name = StringHelper.getString(data, length, 15, ws);
            }

            if ((option & commandMacro) != 0) {
                // This is a command macro, so it has no cell references
                return;
            }

            int pos = length + 15;

            if (data[pos] == cellReference) {
                int sheet = IntegerHelper.getInt(data[pos + 1], data[pos + 2]);
                int row = IntegerHelper.getInt(data[pos + 3], data[pos + 4]);
                int columnMask = IntegerHelper.getInt(data[pos + 5], data[pos + 6]);
                int column = columnMask & 0xff;

                // Check that we are not dealing with offsets
                Assert.verify((columnMask & 0xc0000) == 0);

                NameRange r = new NameRange(sheet, column, row, column, row);
                ranges.add(r);
            } else if (data[pos] == areaReference) {
                int sheet1 = 0;
                int r1 = 0;
                int columnMask = 0;
                int c1 = 0;
                int r2 = 0;
                int c2 = 0;
                NameRange range = null;

                while (pos < data.length) {
                    sheet1 = IntegerHelper.getInt(data[pos + 1], data[pos + 2]);
                    r1 = IntegerHelper.getInt(data[pos + 3], data[pos + 4]);
                    r2 = IntegerHelper.getInt(data[pos + 5], data[pos + 6]);

                    columnMask = IntegerHelper.getInt(data[pos + 7], data[pos + 8]);
                    c1 = columnMask & 0xff;

                    // Check that we are not dealing with offsets
                    Assert.verify((columnMask & 0xc0000) == 0);

                    columnMask = IntegerHelper.getInt(data[pos + 9], data[pos + 10]);
                    c2 = columnMask & 0xff;

                    // Check that we are not dealing with offsets
                    Assert.verify((columnMask & 0xc0000) == 0);

                    range = new NameRange(sheet1, c1, r1, c2, r2);
                    ranges.add(range);

                    pos += 11;
                }
            } else if (data[pos] == subExpression) {
                int sheet1 = 0;
                int r1 = 0;
                int columnMask = 0;
                int c1 = 0;
                int r2 = 0;
                int c2 = 0;
                NameRange range = null;

                // Consume unnecessary parsed tokens
                if (pos < data.length &&
                        data[pos] != cellReference &&
                        data[pos] != areaReference) {
                    if (data[pos] == subExpression) {
                        pos += 3;
                    } else if (data[pos] == union) {
                        pos += 1;
                    }
                }

                while (pos < data.length) {
                    sheet1 = IntegerHelper.getInt(data[pos + 1], data[pos + 2]);
                    r1 = IntegerHelper.getInt(data[pos + 3], data[pos + 4]);
                    r2 = IntegerHelper.getInt(data[pos + 5], data[pos + 6]);

                    columnMask = IntegerHelper.getInt(data[pos + 7], data[pos + 8]);
                    c1 = columnMask & 0xff;

                    // Check that we are not dealing with offsets
                    Assert.verify((columnMask & 0xc0000) == 0);

                    columnMask = IntegerHelper.getInt(data[pos + 9], data[pos + 10]);
                    c2 = columnMask & 0xff;

                    // Check that we are not dealing with offsets
                    Assert.verify((columnMask & 0xc0000) == 0);

                    range = new NameRange(sheet1, c1, r1, c2, r2);
                    ranges.add(range);

                    pos += 11;

                    // Consume unnecessary parsed tokens
                    if (pos < data.length &&
                            data[pos] != cellReference &&
                            data[pos] != areaReference) {
                        if (data[pos] == subExpression) {
                            pos += 3;
                        } else if (data[pos] == union) {
                            pos += 1;
                        }
                    }
                }
            } else {
                String n = name != null ? name : builtInName.getName();
                logger.warn("Cannot read name ranges for " + n +
                        " - setting to empty");
                NameRange range = new NameRange(0, 0, 0, 0, 0);
                ranges.add(range);
            }
        } catch (Throwable t1) {
            // Generate a warning
            // Names are really a nice to have, and we don't want to halt the
            // reading process for functionality that probably won't be used
            logger.warn("Cannot read name");
            name = "ERROR";
        }
    }

    /**
     * Constructs this object from the raw data
     *
     * @param t the raw data
     * @param ws the workbook settings
     * @param ind the index in the name table
     * @param dummy dummy parameter to indicate a biff7 workbook
     */
    NameRecord(Record t, WorkbookSettings ws, int ind, Biff7 dummy) {
        super(t);
        index = ind;
        isbiff8 = false;

        try {
            ranges = new ArrayList();
            byte[] data = getRecord().getData();
            int length = data[3];
            sheetRef = IntegerHelper.getInt(data[8], data[9]);
            name = StringHelper.getString(data, length, 14, ws);

            int pos = length + 14;

            if (pos >= data.length) {
                // There appears to be nothing after the name, so return
                return;
            }

            if (data[pos] == cellReference) {
                int sheet = IntegerHelper.getInt(data[pos + 11], data[pos + 12]);
                int row = IntegerHelper.getInt(data[pos + 15], data[pos + 16]);
                int column = data[pos + 17];

                NameRange r = new NameRange(sheet, column, row, column, row);
                ranges.add(r);
            } else if (data[pos] == areaReference) {
                int sheet1 = 0;
                int r1 = 0;
                int c1 = 0;
                int r2 = 0;
                int c2 = 0;
                NameRange range = null;

                while (pos < data.length) {
                    sheet1 = IntegerHelper.getInt(data[pos + 11], data[pos + 12]);
                    r1 = IntegerHelper.getInt(data[pos + 15], data[pos + 16]);
                    r2 = IntegerHelper.getInt(data[pos + 17], data[pos + 18]);

                    c1 = data[pos + 19];
                    c2 = data[pos + 20];

                    range = new NameRange(sheet1, c1, r1, c2, r2);
                    ranges.add(range);

                    pos += 21;
                }
            } else if (data[pos] == subExpression) {
                int sheet1 = 0;
                int sheet2 = 0;
                int r1 = 0;
                int c1 = 0;
                int r2 = 0;
                int c2 = 0;
                NameRange range = null;

                // Consume unnecessary parsed tokens
                if (pos < data.length &&
                        data[pos] != cellReference &&
                        data[pos] != areaReference) {
                    if (data[pos] == subExpression) {
                        pos += 3;
                    } else if (data[pos] == union) {
                        pos += 1;
                    }
                }

                while (pos < data.length) {
                    sheet1 = IntegerHelper.getInt(data[pos + 11], data[pos + 12]);
                    r1 = IntegerHelper.getInt(data[pos + 15], data[pos + 16]);
                    r2 = IntegerHelper.getInt(data[pos + 17], data[pos + 18]);

                    c1 = data[pos + 19];
                    c2 = data[pos + 20];

                    range = new NameRange(sheet1, c1, r1, c2, r2);
                    ranges.add(range);

                    pos += 21;

                    // Consume unnecessary parsed tokens
                    if (pos < data.length &&
                            data[pos] != cellReference &&
                            data[pos] != areaReference) {
                        if (data[pos] == subExpression) {
                            pos += 3;
                        } else if (data[pos] == union) {
                            pos += 1;
                        }
                    }
                }
            }
        } catch (Throwable t1) {
            // Generate a warning
            // Names are really a nice to have, and we don't want to halt the
            // reading process for functionality that probably won't be used
            logger.warn("Cannot read name.");
            name = "ERROR";
        }
    }

    /**
     * Gets the name
     *
     * @return the strings
     */
    public String getName() {
        return name;
    }

    /**
     * Gets the built in name
     *
     * @return the built in name
     */
    public BuiltInName getBuiltInName() {
        return builtInName;
    }

    /**
     * Gets the array of ranges for this name.  This method is public as it is
     * used from the writable side when copying ranges
     *
     * @return the ranges
     */
    public NameRange[] getRanges() {
        NameRange[] nr = new NameRange[ranges.size()];
        return (NameRange[]) ranges.toArray(nr);
    }

    /**
     * Accessor for the index into the name table
     *
     * @return the 0-based index into the name table
     */
    int getIndex() {
        return index;
    }

    /**
     * The 0-based index sheet reference for a record name
     *     0 is for a global reference
     *
     * @return the sheet reference for name formula
     */
    public int getSheetRef() {
        return sheetRef;
    }

    /**
     * Set the index sheet reference for a record name
     *     0 is for a global reference
     */
    public void setSheetRef(int i) {
        sheetRef = i;
    }

    /**
     * Called when copying a sheet.  Just returns the raw data
     *
     * @return the raw data
     */
    public byte[] getData() {
        return getRecord().getData();
    }

    /**
     * Called when copying to determine whether this is a biff8 name
     *
     * @return TRUE if this is a biff8 name record, FALSE otherwise
     */
    public boolean isBiff8() {
        return isbiff8;
    }

    /**
     * Queries whether this is a global name or not
     *
     * @return TRUE if this is a global name, FALSE otherwise
     */
    public boolean isGlobal() {
        return sheetRef == 0;
    }
}

